<?php

/**
 * @author  Nam Nguyen <nam.ngvan@gmail.com>
 * @version 1.0
 * @since   Apr 17, 2013
 */
class Nuo_Paging implements Countable, IteratorAggregate
{
	
	/**
	 * Page number/ start from 1
	 * @access protected
	 * @var   int
	 */
	protected $_page = 1;

	/**
	 * Limit number of items for each page
	 * @access  protected
	 * @var     int 
	 */
	protected $_limit = NULL;
	
	/**
	 * Total item count of data
	 * @access  protected
	 * @var     int
	 */
	protected $_totalItemCount = NULL;

	/**
	 * Current items count of currently page
	 * @access  protected
	 * @var     int
	 */
	protected $_itemCount = NULL;
	
	/**
	 * Paging adatper
	 */
	protected $_adapter = NULL;
	
	/**
	 * @access  protected
	 * @var     int
	 */
	protected $_offset = NULL;
	
	/**
	 * @access  protected
	 * @var     array/object
	 */
	protected $_items = NULL;

	/**
	 * Total number of pages
	 * @access  protected
	 * @var     int
	 */
	protected $_pageCount = NULL;
	
	/**
	 * Number of paginator items will be display in paginator control, default is 5
	 * @access  protected
	 * @var     int
	 */
	protected $_pageRangeWidth = 5;
	
	/**
	 * Query param to build paginator control
	 * @access protected
	 * @var array  
	 */
	protected $_queryParams = NULL;

	/**
	 * Base query url to build paginator control
	 */
	protected $_queryUrl = NULL;

	/**
	 * Page range start index
	 * @access   protected
	 * @var      int
	 */
	protected $_pageRangeStart = NULL;

	/**
	 * Page range end index
	 * @access   protected
	 * @var      int
	 */
	protected $_pageRangeEnd = NULL;

	/**
	 * @access  private
	 * @param   object/array  $adapter
	 * @param   int           $page
	 * @param   int           $limit
	 * @param   string        $queryUrl
	 * @param   array         $queryParams
	 * @return  void
	 */
	private function __construct($adapter, $page = 1, $limit = NULL, $queryUrl = NULL, $queryParams = NULL)
	{
		$this -> _adapter = $adapter;
		$this -> setLimit($limit);
		$this -> setPage($page);
		$this -> setQueryParams($queryParams);
		$this -> setQueryUrl($queryUrl);
	}

	/**
	 * @param array $params
	 * @return Paging
	 */
	function setQueryParams($params = NULL)
	{
		if (NULL == $params)
		{
			$params = $_GET;
		}
		if (isset($params['page']))
		{
			unset($params['page']);
		}

		if (isset($params['_p']))
		{
			unset($params['_p']);
		}
		$this -> _queryParams = $params;
		return $this;
	}

	/**
	 * @return array
	 */
	function getQueryParams()
	{
		return $this -> _queryParams;
	}

	/**
	 * @param string $queryUrl
	 * @return Paging
	 */
	function setQueryUrl($queryUrl = NULL)
	{
		if (NULL == $queryUrl)
		{
			$queryUrl = Nuo::getRequest() -> getPath();
		}
		$this -> _queryUrl = $queryUrl;
		return $this;
	}

	/**
	 * @return string
	 */
	function getQueryUrl()
	{
		return $this -> _queryUrl;
	}

	function setOffset($offset)
	{
		$this -> _offset = intval($offset);
		if ($this -> _offset < 0)
		{
			$this -> _offset = 0;
		}
		return $this;
	}

	function getOffset()
	{
		if (null == $this -> _offset)
		{
			$this -> _offset = 0;
		}
		return $this -> _offset;
	}

	function getLimit()
	{
		if (NULL == $this -> _limit)
		{
			$this -> _limit = 10;
		}
		return $this -> _limit;
	}

	function setLimit($limit)
	{
		$this -> _limit = intval($limit);
		if ($this -> _limit < 1)
		{
			$this -> _limit = 10;
		}
		$this -> _prepareOffset();
		return $this;

	}

	function _prepareOffset()
	{
		$this -> _offset = ($this -> getPage() - 1) * $this -> getLimit();
	}

	function getPage()
	{
		if (NULL == $this -> _page)
		{
			$this -> _page = 1;
		}
		return $this -> _page;
	}

	function setPage($page)
	{
		$this -> _page = intval($page);
		if ($this -> _page < 1)
		{
			$this -> _page = 1;
		}
		$this -> _prepareOffset();
		return $this -> _page;
	}

	function getItems()
	{
		if (NULL === $this -> _items)
		{
			$this -> _items = $this -> _adapter -> getItems($this -> getOffset(), $this -> getLimit());
		}
		return $this -> _items;
	}

	function getTotalItemCount()
	{
		if (NULL === $this -> _totalItemCount)
		{
			$this -> _totalItemCount = $this -> _adapter -> getTotalCount();
		}
		return $this -> _totalItemCount;
	}

	function getItemCount()
	{
		if (NULL == $this -> _itemCount)
		{
			$this -> _itemCount = count($this -> getItems());
		}
		return $this -> _itemCount;
	}

	/**
	 * Returns a foreach-compatible iterator.
	 *
	 * @return Traversable
	 */
	function getIterator()
	{
		$items = $this -> getItems();

		if (!$items instanceof Traversable)
		{
			$items = new ArrayIterator($items);
		}

		return $items;
	}

	public function count()
	{
		if (NULL == $this -> _pageCount)
		{
			$this -> _pageCount = $this -> getTotalItemCount() / $this -> getLimit();
		}
	}

	public function getTotalPageCount()
	{
		return ceil($this -> getTotalItemCount() / $this -> getLimit());
	}

	public function getPageRangeWidth()
	{
		return $this -> _pageRangeWidth;
	}

	/**
	 * @param int $range
	 * @return Paging
	 */
	public function setPageRange($width)
	{
		$this -> _pageRangeWidth = (int)$width;
		return $this;
	}

	/**
	 * @return void
	 */
	protected function _buildPageRange()
	{
		$cur = $this -> getPage();

		$width = $this -> getPageRangeWidth();

		$min = $cur - floor($width / 2);

		if ($min < 1)
		{
			$min = 1;
		}
		$max = $min + $width - 1;

		if ($max > $this -> getTotalPageCount())
		{
			$max = $this -> getTotalPageCount();
		}

		$min = $max - $width + 1;

		if ($min < 1)
		{
			$min = 1;
		}
		$this -> _pageRangeStart = $min;
		$this -> _pageRangeEnd = $max;
	}

	/**
	 * @return array
	 */
	public function getPageRanges()
	{
		if (NULL === $this -> _pageRangeStart)
		{
			$this -> _buildPageRange();
		}

		return range($this -> _pageRangeStart, $this -> _pageRangeEnd, 1);
	}

	/**
	 * @return int
	 */
	public function getCurrentPageRangeWidth()
	{
		if (NULL === $this -> _pageRangeStart)
		{
			$this -> _buildPageRange();
		}
		return $this -> _pageRangeEnd - $this -> _pageRangeStart;
	}

	/**
	 * @return int
	 */
	public function getPageRangeStart()
	{
		if (NULL === $this -> _pageRangeStart)
		{
			$this -> _buildPageRange();
		}
		return $this -> _pageRangeStart;

	}

	/**
	 * @return int
	 */
	public function getPageRangeEnd()
	{

		if (NULL === $this -> _pageRangeStart)
		{
			$this -> _buildPageRange();
		}
		return $this -> _pageRangeEnd;

	}

	/**
	 * process paginator caculated.
	 * @param string $script
	 * @return string
	 */
	public function getPaginator($script = NULL)
	{
		if (NULL == $script)
		{
			$script = 'core/layout/paging/default';
		}

		return Nuo::getView()->render($script, array('paging' => $this));
	}

	/**
	 * @return string
	 */
	public function getUrl($page)
	{
		$params = $this -> getQueryParams();
		$params['page'] = $page;
		return Nuo::getUrl($this -> getQueryUrl(), $params);
	}
	
	/**
	 * @static
	 * @access  public
	 * @param   select $data
	 * @param   int $page
	 * @param   $limit
	 * @return  Paging
	 */
	static public function factory($data, $page = 1, $limit = NULL, $queryUrl = NULL, $queryParams = NULL)
	{
		if (is_array($data))
		{
			require_once 'nuo/classes/paging/array.class.php';
			$adapter = new Nuo_Paging_Array($data);
		}
		else
		if ($data instanceof Database_Table_Select)
		{
			require_once 'nuo/classes/paging/table.class.php';
			$adapter = new Nuo_Paging_Table($data);
		}
		else
		if ($data instanceof Database_Select)
		{
			require_once 'nuo/classes/paging/select.class.php';
			$adapter = new Nuo_Paging_Select($data);
		}
		
		return new Nuo_Paging($adapter, $page, $limit, $queryParams);
	}
}
